Skip to content

Preserve using-declaration access for inherited methods#995

Open
guitargeek wants to merge 1 commit into
compiler-research:mainfrom
guitargeek:cppyy-220
Open

Preserve using-declaration access for inherited methods#995
guitargeek wants to merge 1 commit into
compiler-research:mainfrom
guitargeek:cppyy-220

Conversation

@guitargeek

Copy link
Copy Markdown
Collaborator

When GetClassDecls<CXXMethodDecl> walked a class's decls and hit a UsingShadowDecl whose target is a method, it pushed the target CXXMethodDecl from the base class. The target's getAccess() returns the access in the base (e.g. protected); the elevated access carried by the using-declaration (public in the introducing class) was silently lost. Consumers like CPyCppyy filter on IsPublicMethod, so a republished overload such as

class Base   { protected: void foo(int, int); };
class Derived: public Base {
public:
    using Base::foo;
    void foo(int);
};

would never reach the Python proxy - calling derived.foo(1, 2) from cppyy raised TypeError: takes at most 1 arguments (2 given) (cppyy issue compiler-research/cppyy#220).

Push the UsingShadowDecl itself (for the non-constructor case; constructor-using-shadows still go through findInheritingConstructor) and teach CheckMethodAccess to consult USD->getAccess(). A small UnwrapUsingShadowToFunction helper unwraps the shadow at the entry of every API that downcasts a TCppFunction_t to FunctionDecl (return type, arg counts/types/names/defaults, signature, IsConst/Static/Virtual/Constructor/Destructor/Templated/Explicit, function address, operator arity).

MakeFunctionCallable keeps the unwrapped target as the wrapped function but threads a relaxAccessControl flag into make_wrapper when the caller passed a using-shadow: the generated wrapper still references the target by its original qualified name (e.g. ((Base*)obj)->Base::foo(...)), so access control has to be relaxed during wrapper compilation for it to compile. The runtime call is well-defined because the using-declaration made the member reachable through the derived class.

Adds a FunctionReflection_GetClassMethods_UsingShadowAccess regression test covering both the public-promoted case from the issue and a protected-using control case (which must stay hidden).

Fixes: compiler-research/cppyy#220

@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.62%. Comparing base (84e1ff5) to head (7ac41c3).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #995      +/-   ##
==========================================
+ Coverage   86.56%   86.62%   +0.05%     
==========================================
  Files          23       23              
  Lines        5621     5643      +22     
==========================================
+ Hits         4866     4888      +22     
  Misses        755      755              
Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.66% <100.00%> (+0.07%) ⬆️

... and 1 file with indirect coverage changes

Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.66% <100.00%> (+0.07%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

if (auto* USD = llvm::dyn_cast_or_null<UsingShadowDecl>(D)) {
if (llvm::isa_and_nonnull<CXXMethodDecl>(USD->getTargetDecl()))
return USD->getAccess() == AS;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using UnwrapUsingShadowToFunction?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is explained now in the inline comments.

@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@guitargeek

Copy link
Copy Markdown
Collaborator Author

FYI @vgvassilev : this fix is not strictly needed for the ROOT migration, as the ROOT pythonizations tests have evolved to not hit this edge case anymore. So reviewing this PR is not a priority.

@vgvassilev

Copy link
Copy Markdown
Contributor

FYI @vgvassilev : this fix is not strictly needed for the ROOT migration, as the ROOT pythonizations tests have evolved to not hit this edge case anymore. So reviewing this PR is not a priority.

Looks quite good already -- can we make sure the codecov reports are addressed with tests -- should be ready to go.

@guitargeek guitargeek force-pushed the cppyy-220 branch 2 times, most recently from 5086ccf to d91da57 Compare June 28, 2026 09:18
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Ctor.Invoke((void*)&object, {}, /*self=*/nullptr);
ASSERT_TRUE(object);

int a = 3, b = 7, result = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]

Suggested change
int a = 3, b = 7, result = 0;
int a = 3;
int b = 7;
int result = 0;

int a = 3, b = 7, result = 0;
std::array<void*, 2> args = {(void*)&a, (void*)&b};
Call.Invoke(&result, {args.data(), /*args_size=*/2}, object);
EXPECT_EQ(result, a * 100 + b);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: '*' has higher precedence than '+'; add parentheses to explicitly specify the order of operations [readability-math-missing-parentheses]

  EXPECT_EQ(result, a * 100 + b);
                    ^

@vgvassilev vgvassilev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm!

When `GetClassDecls<CXXMethodDecl>` walked a class's decls and hit a
`UsingShadowDecl` whose target is a method, it pushed the *target*
`CXXMethodDecl` from the base class. The target's `getAccess()` returns
the access in the base (e.g. `protected`); the elevated access carried
by the using-declaration (`public` in the introducing class) was
silently lost. Consumers like CPyCppyy filter on `IsPublicMethod`, so a
republished overload such as

    class Base   { protected: void foo(int, int); };
    class Derived: public Base {
    public:
        using Base::foo;
        void foo(int);
    };

would never reach the Python proxy - calling `derived.foo(1, 2)` from
cppyy raised `TypeError: takes at most 1 arguments (2 given)`
(cppyy issue compiler-research/cppyy#220).

Push the `UsingShadowDecl` itself (for the non-constructor case;
constructor-using-shadows still go through `findInheritingConstructor`)
and teach `CheckMethodAccess` to consult `USD->getAccess()`. A small
`UnwrapUsingShadowToFunction` helper unwraps the shadow at the entry
of every API that downcasts a `TCppFunction_t` to `FunctionDecl`
(return type, arg counts/types/names/defaults, signature,
IsConst/Static/Virtual/Constructor/Destructor/Templated/Explicit,
function address, operator arity).

`MakeFunctionCallable` keeps the unwrapped target as the wrapped
function but threads a `relaxAccessControl` flag into `make_wrapper`
when the caller passed a using-shadow: the generated wrapper still
references the target by its original qualified name (e.g.
`((Base*)obj)->Base::foo(...)`), so access control has to be relaxed
during wrapper compilation for it to compile. The runtime call is
well-defined because the using-declaration made the member reachable
through the derived class.

Adds a `FunctionReflection_GetClassMethods_UsingShadowAccess`
regression test covering both the public-promoted case from the issue
and a protected-using control case (which must stay hidden).

Fixes: compiler-research/cppyy#220
@guitargeek

Copy link
Copy Markdown
Collaborator Author

I'm just fixing the clang tidy warnings

@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression in using protected base class members as public methods in derived classes

2 participants